package deconstruction.asm;

import static org.objectweb.asm.Opcodes.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import net.minecraft.item.crafting.IRecipe;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraft.util.StringUtils;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import com.google.common.base.Strings;
import com.google.common.reflect.ClassPath.ClassInfo;

import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import deconstruction.common.Info;

public class deconstructionClassTransformer implements IClassTransformer {

	private boolean obf = true;
	@Override
	public byte[] transform(String arg0, String arg1, byte[] arg2) {

		if(arg0.equals("net.minecraft.client.main.Main")){
			obf = false;
		}

		if(arg0.equals("ic2.core.AdvCraftingRecipeManager")){
			System.out.println("[deconstruction] About to patch "+arg0+".class");
			return patchIC2Classes(arg0, arg2, obf);
		}
		if (arg0.equals("aaf")) {
			System.out.println("[deconstruction] About to patch "+arg0+".class");
			return patchVanillaClasses(arg0, arg2, true);
		}
		if (arg0.equals("net.minecraft.item.crafting.CraftingManager")) {
			System.out.println("[deconstruction] About to patch "+arg0+".class");
			return patchVanillaClasses(arg0, arg2, false);
		}

		return arg2;
	}	

	public byte[] patchIC2Classes(String name, byte[] bytes, boolean obfuscated) {

		String targetMethodName = "addRecipe";
		String targetMethodName2 = "addShapelessRecipe";
		ClassNode classNode = new ClassNode();
		ClassReader classReader = new ClassReader(bytes);
		classReader.accept(classNode, 0);
		if(obfuscated == true){
			targetMethodName ="addRecipe";
			targetMethodName2 ="addShapelessRecipe";

			Iterator<MethodNode> methods = classNode.methods.iterator();
			while(methods.hasNext())
			{
				MethodNode m = methods.next();
				int fdiv_index = -1;

				if ((m.name.equals(targetMethodName) && m.desc.equals("(Lye;[Ljava/lang/Object;)Laai;")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;
					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == ARETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();

					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new VarInsnNode(ALOAD, 9));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapedRecipe","(Laai;)V"));
					m.instructions.insertBefore(targetNode, toInject);
				}

				if ((m.name.equals(targetMethodName2) && m.desc.equals("(Lye;[Ljava/lang/Object;)V")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;

					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == RETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();


					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new TypeInsnNode(NEW, "aaj"));
					toInject.add(new InsnNode(DUP));
					toInject.add(new VarInsnNode(ALOAD, 1));
					toInject.add(new VarInsnNode(ALOAD,3));
					toInject.add(new MethodInsnNode(INVOKESPECIAL,"aaj","<init>","(Lye;Ljava/util/List;)V"));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapelessRecipe","(Laaj;)V"));

					m.instructions.insertBefore(targetNode, toInject);

					System.out.println("[deconstruction] Patching Complete!");
					break;
				}


			}
		}else{
			targetMethodName ="addRecipe";
			targetMethodName2 ="addShapelessRecipe";

			Iterator<MethodNode> methods = classNode.methods.iterator();
			while(methods.hasNext())
			{
				MethodNode m = methods.next();
				int fdiv_index = -1;

				if ((m.name.equals(targetMethodName) && m.desc.equals("(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;
					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == ALOAD)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();
					toInject.add(new MethodInsnNode(INVOKESTATIC, "deconstruction/deconTable/DeconstructionManager", "getInstance", "()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new VarInsnNode(ALOAD, 1));
					toInject.add(new VarInsnNode(ALOAD, 2));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL, "deconstruction/deconTable/DeconstructionManager", "addRecipe", "(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)Lnet/minecraft/item/crafting/ShapedRecipes;"));
					toInject.add(new InsnNode(POP));

					m.instructions.insertBefore(targetNode, toInject);

				}

				if ((m.name.equals(targetMethodName2) && m.desc.equals("(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;

					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == ALOAD)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();

					toInject.add(new MethodInsnNode(INVOKESTATIC, "deconstruction/deconTable/DeconstructionManager", "getInstance", "()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new VarInsnNode(ALOAD, 1));
					toInject.add(new VarInsnNode(ALOAD, 2));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL, "deconstruction/deconTable/DeconstructionManager", "addShapelessRecipe", "(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V"));
					m.instructions.insertBefore(targetNode, toInject);

					System.out.println("[deconstruction] Patching Complete!");
					break;
				}

			}

		}
		ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
		classNode.accept(writer);
		return writer.toByteArray();


	}





	public byte[] patchVanillaClasses(String name, byte[] bytes, boolean obfuscated) {
		String targetMethodName = "";
		String targetMethodName2 = "";
		ClassNode classNode = new ClassNode();
		ClassReader classReader = new ClassReader(bytes);
		classReader.accept(classNode, 0);
		if(obfuscated == true){
			targetMethodName ="a";
			targetMethodName2 ="b";

			Iterator<MethodNode> methods = classNode.methods.iterator();
			while(methods.hasNext())
			{
				MethodNode m = methods.next();
				int fdiv_index = -1;

				if ((m.name.equals(targetMethodName) && m.desc.equals("(Lye;[Ljava/lang/Object;)Laai;")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;
					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == ARETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();

					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new VarInsnNode(ALOAD, 9));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapedRecipe","(Laai;)V"));
					m.instructions.insertBefore(targetNode, toInject);
				}

				if ((m.name.equals(targetMethodName2) && m.desc.equals("(Lye;[Ljava/lang/Object;)V")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;

					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == RETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();


					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new TypeInsnNode(NEW, "aaj"));
					toInject.add(new InsnNode(DUP));
					toInject.add(new VarInsnNode(ALOAD, 1));
					toInject.add(new VarInsnNode(ALOAD,3));
					toInject.add(new MethodInsnNode(INVOKESPECIAL,"aaj","<init>","(Lye;Ljava/util/List;)V"));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapelessRecipe","(Laaj;)V"));

					m.instructions.insertBefore(targetNode, toInject);

					System.out.println("[deconstruction] Patching Complete!");
					break;
				}


			}
		}else{
			targetMethodName ="addRecipe";
			targetMethodName2 ="addShapelessRecipe";

			Iterator<MethodNode> methods = classNode.methods.iterator();
			while(methods.hasNext())
			{
				MethodNode m = methods.next();
				int fdiv_index = -1;

				if ((m.name.equals(targetMethodName) && m.desc.equals("(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)Lnet/minecraft/item/crafting/ShapedRecipes;")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;
					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == ARETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();

					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new VarInsnNode(ALOAD, 9));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapedRecipe","(Lnet/minecraft/item/crafting/ShapedRecipes;)V"));
					m.instructions.insertBefore(targetNode, toInject);

				}

				if ((m.name.equals(targetMethodName2) && m.desc.equals("(Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V")))
				{
					AbstractInsnNode currentNode = null;
					AbstractInsnNode targetNode = null;

					@SuppressWarnings("unchecked")
					Iterator<AbstractInsnNode> iter = m.instructions.iterator();

					int index = -1;

					while (iter.hasNext())
					{
						index++;
						currentNode = iter.next();
						if (currentNode.getOpcode() == RETURN)
						{
							targetNode = currentNode;
							fdiv_index = index;
						}
					}

					InsnList toInject = new InsnList();

					toInject.add(new MethodInsnNode(INVOKESTATIC,"deconstruction/deconTable/DeconstructionManager","getInstance","()Ldeconstruction/deconTable/DeconstructionManager;"));
					toInject.add(new TypeInsnNode(NEW, "net/minecraft/item/crafting/ShapelessRecipes"));
					toInject.add(new InsnNode(DUP));
					toInject.add(new VarInsnNode(ALOAD, 1));
					toInject.add(new VarInsnNode(ALOAD,3));
					toInject.add(new MethodInsnNode(INVOKESPECIAL,"net/minecraft/item/crafting/ShapelessRecipes","<init>","(Lnet/minecraft/item/ItemStack;Ljava/util/List;)V"));
					toInject.add(new MethodInsnNode(INVOKEVIRTUAL,"deconstruction/deconTable/DeconstructionManager","addShapelessRecipe","(Lnet/minecraft/item/crafting/ShapelessRecipes;)V"));

					m.instructions.insertBefore(targetNode, toInject);

					System.out.println("[deconstruction] Patching Complete!");
					break;
				}


			}
		}
		ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
		classNode.accept(writer);
		return writer.toByteArray();
	}

}

